package com.aurora.service.impl;

import com.alibaba.fastjson.JSON;
import com.aurora.model.dto.*;
import com.aurora.entity.Article;
import com.aurora.entity.ArticleTag;
import com.aurora.entity.Category;
import com.aurora.entity.Tag;
import com.aurora.enums.FileExtEnum;
import com.aurora.enums.FilePathEnum;
import com.aurora.exception.BizException;
import com.aurora.mapper.ArticleMapper;
import com.aurora.mapper.ArticleTagMapper;
import com.aurora.mapper.CategoryMapper;
import com.aurora.mapper.TagMapper;
import com.aurora.service.ArticleService;
import com.aurora.service.ArticleTagService;
import com.aurora.service.RedisService;
import com.aurora.service.TagService;
import com.aurora.strategy.context.RedisStrategyContext;
import com.aurora.strategy.context.SearchStrategyContext;
import com.aurora.strategy.context.UploadStrategyContext;
import com.aurora.util.BeanCopyUtil;
import com.aurora.util.PageUtil;
import com.aurora.util.UserUtil;
import com.aurora.model.vo.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.aurora.constant.RabbitMQConstant.SUBSCRIBE_EXCHANGE;
import static com.aurora.constant.RedisConstant.*;
import static com.aurora.enums.ArticleStatusEnum.*;
import static com.aurora.enums.StatusCodeEnum.ARTICLE_ACCESS_FAIL;

@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    @Autowired
    private ArticleService articleService;

    @Autowired
    private ArticleTagMapper articleTagMapper;

    @Autowired
    private CategoryMapper categoryMapper;

    @Autowired
    private TagMapper tagMapper;

    @Autowired
    private TagService tagService;

    @Autowired
    private ArticleTagService articleTagService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private UploadStrategyContext uploadStrategyContext;

    @Autowired
    private SearchStrategyContext searchStrategyContext;

    @Autowired
    private RedisStrategyContext redisStrategyContext;

    @SneakyThrows // 自动抛出检查异常，避免显式捕获
    @Override
    public TopAndFeaturedArticlesDTO listTopAndFeaturedArticles() {
        // 从数据库或其他数据源获取前几篇文章的列表
        List<ArticleCardDTO> articleCardDTOs = this.baseMapper.listTopAndFeaturedArticles();

        // 如果没有文章数据，返回一个空的 TopAndFeaturedArticlesDTO 对象
        if (articleCardDTOs.isEmpty()) {
            return new TopAndFeaturedArticlesDTO();
        }
        // 如果文章数量大于 3，截取前三篇文章
        else if (articleCardDTOs.size() > 3) {
            articleCardDTOs = articleCardDTOs.subList(0, 3);
        }

        // 创建 TopAndFeaturedArticlesDTO 对象，并设置“头条文章”
        TopAndFeaturedArticlesDTO topAndFeaturedArticlesDTO = new TopAndFeaturedArticlesDTO();
        topAndFeaturedArticlesDTO.setTopArticle(articleCardDTOs.get(0)); // 设置头条文章为列表中的第一篇
        articleCardDTOs.remove(0); // 移除头条文章
        topAndFeaturedArticlesDTO.setFeaturedArticles(articleCardDTOs); // 将剩余的文章作为推荐文章设置

        // 返回最终的 TopAndFeaturedArticlesDTO 对象
        return topAndFeaturedArticlesDTO;
    }

    @SneakyThrows // 自动抛出检查异常，避免显式捕获
    @Override
    public PageResultDTO<ArticleCardDTO> listArticles() {
        // 创建查询条件，查找未删除且状态为 1 或 2 的文章
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>()
                .eq(Article::getIsDelete, 0)  // 过滤掉已删除的文章
                .in(Article::getStatus, 1, 2); // 只查询状态为 1 或 2 的文章

        // 异步获取文章总数
        CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> this.baseMapper.selectCount(queryWrapper));

        // 获取当前页面的文章列表
        List<ArticleCardDTO> articles = this.baseMapper.listArticles(PageUtil.getLimitCurrent(), PageUtil.getSize());

        // 返回分页结果，包含文章列表和异步获取的文章总数
        return new PageResultDTO<>(articles, asyncCount.get());
    }

    @SneakyThrows // 自动抛出检查异常，避免显式捕获
    @Override
    public PageResultDTO<ArticleCardDTO> listArticlesByCategoryId(Integer categoryId) {
        // 创建查询条件，查找指定类别的文章
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>().eq(Article::getCategoryId, categoryId);

        // 异步获取该类别文章的总数
        CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> this.baseMapper.selectCount(queryWrapper));

        // 获取当前页面的指定类别文章列表
        List<ArticleCardDTO> articles = this.baseMapper.getArticlesByCategoryId(PageUtil.getLimitCurrent(), PageUtil.getSize(), categoryId);

        // 返回分页结果，包含文章列表和异步获取的文章总数
        return new PageResultDTO<>(articles, asyncCount.get());
    }

    @SneakyThrows // 自动抛出检查异常，避免显式捕获
    @Override
    public ArticleDTO getArticleById(Integer articleId) {
        // 根据文章 ID 查询文章信息
        Article articleForCheck = this.baseMapper.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articleId));

        // 如果文章不存在，返回 null
        if (Objects.isNull(articleForCheck)) {
            return null;
        }

        // 如果文章状态为 2（需要权限），则检查当前用户是否有访问权限
        if (articleForCheck.getStatus().equals(2)) {
            Boolean isAccess;
            try {
                // 查询当前用户是否已被授权访问该文章
                isAccess = redisService.sIsMember(ARTICLE_ACCESS + UserUtil.getUserDetailsDTO().getId(), articleId);
            } catch (Exception exception) {
                // 如果查询过程中发生异常，则抛出自定义异常
                throw new BizException(ARTICLE_ACCESS_FAIL);
            }
            // 如果没有权限，则抛出异常
            if (isAccess.equals(false)) {
                throw new BizException(ARTICLE_ACCESS_FAIL);
            }
        }

        // 更新文章的查看次数
        updateArticleViewsCount(articleId);

        // 异步获取当前文章的详细信息
        CompletableFuture<ArticleDTO> asyncArticle = CompletableFuture.supplyAsync(() -> this.baseMapper.getArticleById(articleId));

        // 异步获取上一篇文章信息，如果没有上一篇，则获取最后一篇文章
        CompletableFuture<ArticleCardDTO> asyncPreArticle = CompletableFuture.supplyAsync(() -> {
            ArticleCardDTO preArticle = this.baseMapper.getPreArticleById(articleId);
            if (Objects.isNull(preArticle)) {
                preArticle = this.baseMapper.getLastArticle();
            }
            return preArticle;
        });

        // 异步获取下一篇文章信息，如果没有下一篇，则获取第一篇文章
        CompletableFuture<ArticleCardDTO> asyncNextArticle = CompletableFuture.supplyAsync(() -> {
            ArticleCardDTO nextArticle = this.baseMapper.getNextArticleById(articleId);
            if (Objects.isNull(nextArticle)) {
                nextArticle = this.baseMapper.getFirstArticle();
            }
            return nextArticle;
        });

        // 等待异步任务完成，获取当前文章
        ArticleDTO article = asyncArticle.get();

        // 如果文章不存在，返回 null
        if (Objects.isNull(article)) {
            return null;
        }

        // 从 Redis 获取文章的观看次数
        Double score = redisService.zScore(ARTICLE_VIEWS_COUNT, articleId);
        // 如果获取到观看次数，则设置到文章对象中
        if (Objects.nonNull(score)) {
            article.setViewCount(score.intValue());
        }

        // 设置上一篇和下一篇文章信息
        article.setPreArticleCard(asyncPreArticle.get());
        article.setNextArticleCard(asyncNextArticle.get());

        // 返回完整的文章对象
        return article;
    }

    @Override
    public void accessArticle(ArticlePasswordVO articlePasswordVO) {
        // 查询文章信息，检查是否存在
        Article article = this.baseMapper.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articlePasswordVO.getArticleId()));

        // 如果文章不存在，抛出业务异常
        if (Objects.isNull(article)) {
            throw new BizException("文章不存在");
        }

        // 检查文章密码是否与输入密码匹配
        if (article.getPassword().equals(articlePasswordVO.getArticlePassword())) {
            // 如果密码正确，将当前用户的 ID 和文章 ID 加入 Redis 中，标记该用户已访问过该文章
            redisService.sAdd(ARTICLE_ACCESS + UserUtil.getUserDetailsDTO().getId(), articlePasswordVO.getArticleId());
        } else {
            // 如果密码不匹配，抛出密码错误的异常
            throw new BizException("密码错误");
        }
    }

    @SneakyThrows // 自动抛出检查异常，避免显式捕获
    @Override
    public PageResultDTO<ArticleCardDTO> listArticlesByTagId(Integer tagId) {
        // 构建查询条件，筛选指定标签的文章
        LambdaQueryWrapper<ArticleTag> queryWrapper = new LambdaQueryWrapper<ArticleTag>().eq(ArticleTag::getTagId, tagId);

        // 异步获取符合条件的文章总数
        CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> articleTagMapper.selectCount(queryWrapper));

        // 获取当前分页的文章列表
        List<ArticleCardDTO> articles = this.baseMapper.listArticlesByTagId(PageUtil.getLimitCurrent(), PageUtil.getSize(), tagId);

        // 返回分页结果，包含文章列表和异步获取的文章总数
        return new PageResultDTO<>(articles, asyncCount.get());
    }

    @SneakyThrows // 自动抛出检查异常，避免显式捕获
    @Override
    public PageResultDTO<ArchiveDTO> listArchives() {
        // 构建查询条件，筛选未删除且已发布的文章
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>()
                .eq(Article::getIsDelete, 0)
                .eq(Article::getStatus, 1);

        // 异步获取符合条件的文章总数
        CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> this.baseMapper.selectCount(queryWrapper));

        // 获取当前分页的文章列表
        List<ArticleCardDTO> articles = this.baseMapper.listArchives(PageUtil.getLimitCurrent(), PageUtil.getSize());

        // 按年月将文章进行分组
        HashMap<String, List<ArticleCardDTO>> map = new HashMap<>();
        for (ArticleCardDTO article : articles) {
            LocalDateTime createTime = article.getCreateTime();
            int month = createTime.getMonth().getValue();
            int year = createTime.getYear();
            String key = year + "-" + month;

            // 如果该年月没有记录，则创建一个新的列表
            if (Objects.isNull(map.get(key))) {
                List<ArticleCardDTO> articleCardDTOS = new ArrayList<>();
                articleCardDTOS.add(article);
                map.put(key, articleCardDTOS);
            } else {
                // 否则，将文章加入现有的列表
                map.get(key).add(article);
            }
        }

        // 将分组后的数据转换成归档列表
        List<ArchiveDTO> archiveDTOs = new ArrayList<>();
        map.forEach((key, value) -> archiveDTOs.add(ArchiveDTO.builder().Time(key).articles(value).build()));

        // 按时间降序排序
        archiveDTOs.sort((o1, o2) -> {
            String[] o1s = o1.getTime().split("-");
            String[] o2s = o2.getTime().split("-");
            int o1Year = Integer.parseInt(o1s[0]);
            int o1Month = Integer.parseInt(o1s[1]);
            int o2Year = Integer.parseInt(o2s[0]);
            int o2Month = Integer.parseInt(o2s[1]);

            if (o1Year > o2Year) {
                return -1;
            } else if (o1Year < o2Year) {
                return 1;
            } else {
                return Integer.compare(o2Month, o1Month);
            }
        });

        // 返回分页结果，包含归档列表和异步获取的文章总数
        return new PageResultDTO<>(archiveDTOs, asyncCount.get());
    }

    @SneakyThrows
    @Override
    public PageResultDTO<ArticleAdminDTO> listArticlesAdmin(ConditionVO conditionVO) {
        // 异步获取文章总数
        CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> this.baseMapper.countArticleAdmins(conditionVO));

        // 获取当前分页的文章列表
        List<ArticleAdminDTO> articleAdminDTOs = this.baseMapper.listArticlesAdmin(PageUtil.getLimitCurrent(), PageUtil.getSize(), conditionVO);

        // 从 Redis 获取所有文章的观看次数
        Map<Object, Double> viewsCountMap = redisService.zAllScore(ARTICLE_VIEWS_COUNT);

        // 遍历文章列表，填充观看次数
        articleAdminDTOs.forEach(item -> {
            Double viewsCount = viewsCountMap.get(item.getId());
            if (Objects.nonNull(viewsCount)) {
                item.setViewsCount(viewsCount.intValue());
            }
        });

        // 返回分页结果
        return new PageResultDTO<>(articleAdminDTOs, asyncCount.get());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdateArticle(ArticleVO articleVO) {
        // 保存文章类别
        Category category = saveArticleCategory(articleVO);
        Article article = BeanCopyUtil.copyObject(articleVO, Article.class);

        // 如果有类别，设置类别 ID
        if (Objects.nonNull(category)) {
            article.setCategoryId(category.getId());
        }

        // 获取旧文章（如果存在）
        Article oldArticle = this.getById(articleVO.getId());

        // 如果旧文章封面不同，移除旧封面缓存，添加新封面缓存
        if (Objects.nonNull(oldArticle) &&
                StringUtils.isNotBlank(articleVO.getArticleCover()) &&
                !articleVO.getArticleCover().equals(oldArticle.getArticleCover())) {
            redisStrategyContext.executeRedisRemoveStrategy(PIC_ARTICLE_DB, oldArticle.getArticleCover());
        }
        if (StringUtils.isNotBlank(articleVO.getArticleCover())) {
            redisStrategyContext.executeRedisAddStrategy(PIC_ARTICLE_DB, articleVO.getArticleCover());
        }

        // 如果旧文章存在，移除旧文章内容中的图片缓存
        if (Objects.nonNull(oldArticle)) {
            Set<String> oldMdImgUrl = getMdImgUrl(oldArticle.getArticleContent());
            oldMdImgUrl.forEach(o -> redisStrategyContext
                    .executeRedisRemoveStrategy(PIC_ARTICLE_DB, o));
        }

        // 获取新文章内容中的图片，添加到缓存
        Set<String> mdImgUrl = getMdImgUrl(articleVO.getArticleContent());
        mdImgUrl.forEach(m -> redisStrategyContext
                .executeRedisAddStrategy(PIC_ARTICLE_DB, m));

        // 设置文章的用户 ID
        article.setUserId(UserUtil.getUserDetailsDTO().getUserInfoId());

        // 保存或更新文章
        articleService.saveOrUpdate(article);

        // 保存文章标签
        this.saveArticleTag(articleVO, article.getId());

        // 如果文章是已发布状态，推送更新消息
        if (article.getStatus().equals(1)) {
            rabbitTemplate.convertAndSend(SUBSCRIBE_EXCHANGE, "*", new Message(JSON.toJSONBytes(article.getId()), new MessageProperties()));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateArticleTopAndFeatured(ArticleTopFeaturedVO articleTopFeaturedVO) {
        Article article = Article.builder()
                .id(articleTopFeaturedVO.getId())
                .isTop(articleTopFeaturedVO.getIsTop())
                .isFeatured(articleTopFeaturedVO.getIsFeatured())
                .build();
        this.baseMapper.updateById(article);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateArticleDelete(DeleteVO deleteVO) {
        // 将传入的文章ID列表转换为Article对象列表，并设置删除状态
        List<Article> articles = deleteVO.getIds().stream()
                .map(id -> Article.builder()
                        .id(id)
                        .isDelete(deleteVO.getIsDelete())
                        .build())
                .collect(Collectors.toList());

        // 批量更新文章的删除状态
        articleService.updateBatchById(articles);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteArticles(List<Integer> articleIds) {
        // 查询文章列表
        List<Article> articleList = this.baseMapper.selectList(new QueryWrapper<Article>()
                .in("id", articleIds));

        // 遍历文章，清理缓存
        for (Article article : articleList) {
            // 删除文章封面缓存
            redisStrategyContext.executeRedisRemoveStrategy(PIC_ARTICLE_DB, article.getArticleCover());

            // 删除文章内容中的图片缓存
            Set<String> mdImgUrl = getMdImgUrl(article.getArticleContent());
            mdImgUrl.forEach(m -> redisStrategyContext
                    .executeRedisRemoveStrategy(PIC_ARTICLE_DB, m));
        }

        // 删除文章标签
        articleTagMapper.delete(new LambdaQueryWrapper<ArticleTag>()
                .in(ArticleTag::getArticleId, articleIds));

        // 批量删除文章
        this.baseMapper.deleteBatchIds(articleIds);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ArticleAdminViewDTO getArticleByIdAdmin(Integer articleId) {
        // 查询文章
        Article article = this.baseMapper.selectById(articleId);

        // 查询分类
        Category category = categoryMapper.selectById(article.getCategoryId());
        String categoryName = null;
        if (Objects.nonNull(category)) {
            categoryName = category.getCategoryName();
        }

        // 查询标签
        List<String> tagNames = tagMapper.listTagNamesByArticleId(articleId);

        // 将文章数据复制到DTO
        ArticleAdminViewDTO articleAdminViewDTO = BeanCopyUtil.copyObject(article, ArticleAdminViewDTO.class);
        articleAdminViewDTO.setCategoryName(categoryName);
        articleAdminViewDTO.setTagNames(tagNames);

        return articleAdminViewDTO;
    }

    @Override
    public List<String> exportArticles(List<Integer> articleIds) {
        // 根据文章ID列表查询对应的文章标题和内容
        List<Article> articles = this.baseMapper.selectList(new LambdaQueryWrapper<Article>()
                .select(Article::getArticleTitle, Article::getArticleContent)
                .in(Article::getId, articleIds));

        List<String> urls = new ArrayList<>();

        // 遍历每篇文章，上传其内容并保存返回的URL
        for (Article article : articles) {
            try (ByteArrayInputStream inputStream = new ByteArrayInputStream(article.getArticleContent().getBytes())) {
                // 执行上传操作，将文章内容上传至指定存储路径，生成URL并保存
                String url = uploadStrategyContext.executeUploadStrategy(article.getArticleTitle() + FileExtEnum.MD.getExtName(), inputStream, FilePathEnum.MD.getPath());
                urls.add(url);
            } catch (Exception e) {
                // 捕获上传异常并抛出业务异常
                e.printStackTrace();
                throw new BizException("导出文章失败:" + e.getMessage());
            }
        }
        // 返回上传后的文章URL列表
        return urls;
    }

    @Override
    public List<ArticleSearchDTO> listArticlesBySearch(ConditionVO condition) {
        return searchStrategyContext.executeSearchStrategy(condition.getKeywords());
    }

    public void updateArticleViewsCount(Integer articleId) {
        redisService.zIncr(ARTICLE_VIEWS_COUNT, articleId, 1D);
    }

    private Category saveArticleCategory(ArticleVO articleVO) {
        Category category = categoryMapper.selectOne(new LambdaQueryWrapper<Category>()
                .eq(Category::getCategoryName, articleVO.getCategoryName()));
        if (Objects.isNull(category) && !articleVO.getStatus().equals(DRAFT.getStatus())) {
            category = Category.builder()
                    .categoryName(articleVO.getCategoryName())
                    .build();
            categoryMapper.insert(category);
        }
        return category;
    }

    public void saveArticleTag(ArticleVO articleVO, Integer articleId) {
        if (Objects.nonNull(articleVO.getId())) {
            articleTagMapper.delete(new LambdaQueryWrapper<ArticleTag>()
                    .eq(ArticleTag::getArticleId, articleVO.getId()));
        }
        List<String> tagNames = articleVO.getTagNames();
        if (CollectionUtils.isNotEmpty(tagNames)) {
            List<Tag> existTags = tagService.list(new LambdaQueryWrapper<Tag>()
                    .in(Tag::getTagName, tagNames));
            List<String> existTagNames = existTags.stream()
                    .map(Tag::getTagName)
                    .collect(Collectors.toList());
            List<Integer> existTagIds = existTags.stream()
                    .map(Tag::getId)
                    .collect(Collectors.toList());
            tagNames.removeAll(existTagNames);
            if (CollectionUtils.isNotEmpty(tagNames)) {
                List<Tag> tags = tagNames.stream().map(item -> Tag.builder()
                                .tagName(item)
                                .build())
                        .collect(Collectors.toList());
                tagService.saveBatch(tags);
                List<Integer> tagIds = tags.stream()
                        .map(Tag::getId)
                        .collect(Collectors.toList());
                existTagIds.addAll(tagIds);
            }
            List<ArticleTag> articleTags = existTagIds.stream().map(item -> ArticleTag.builder()
                            .articleId(articleId)
                            .tagId(item)
                            .build())
                    .collect(Collectors.toList());
            articleTagService.saveBatch(articleTags);
        }
    }

    public static Set<String> getMdImgUrl(String mdStr) {
        Set<String> imgSet = new HashSet<>();
        Pattern pattern = Pattern.compile("!\\[(.*?)\\]\\((.*?)\\)", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(mdStr);
        while (matcher.find()) {
            String imgUrl = matcher.group(2);
            imgSet.add(imgUrl);
        }
        return imgSet;
    }

    @Override
    public String saveArticleImages(MultipartFile file) {
        String path = uploadStrategyContext.executeUploadStrategy(file, FilePathEnum.ARTICLE.getPath());
        redisStrategyContext.executeRedisAddStrategy(PIC_ARTICLE_ALL, path);
        return path;
    }
}
